using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading;

namespace PoolKit.Sample
{
    class MainClass
    {
        private const int TestByteArraySize = 16000;
        private const int NumberOfIterations = 1000;
        private const int ReferenceTypesArrayLength = 16000;
        private static byte[] _bytes; // needed for JIT-compiler not to throw away array creation entirely in Release configuration

        public static void Main (string[] args)
        {
            GivingUpResourcesDemo();

            TestPerformanceOfByteArraysAllocationsStraightVersusPrePooled();

            TestPerformanceOfArraysOfReferenceAllocationsStraightVersusPrePooled();

            Console.WriteLine("Hit <Enter> to quit");
            Console.ReadLine();
        }

        private static void GivingUpResourcesDemo()
        {
            Console.WriteLine("Dynamic pool and giving up resources demo starting...");
            Console.WriteLine();

            const int pooledResourcesCount = 5;

            Console.WriteLine(
                String.Format("Wait until the pool is full (has created the following number of resources: {0}), then hit <Enter> to start taking stuff from pool.",
                pooledResourcesCount));

            using (var pool = Pools.CreateDynamicPool(
                Enumerable.Empty<string>(),
                new ReusePreparationStrategyEmpty<string>(),
                new RepairStrategyNotSupportingRepairs<string>(),
                new ResourceCreationFunction<string>(
                    () =>
                        {
                            Console.WriteLine(" Producer: creating a new resource...");

                            return Guid.NewGuid().ToString("N");

                        }, ThreadingContract.Multithreaded),
                pooledResourcesCount,
                TimeSpan.FromDays(1)))
            {
                Console.ReadLine();
                Console.WriteLine("Now taking resources from pool (watch how the pool creates replacements for them)...");

                for (int i = 0; i < pooledResourcesCount; i++)
                {
                    string newResource = pool.GiveUp();

                    Console.WriteLine(String.Format(" Consumer: got a new resource from pool: {0}", newResource));
                    Thread.Sleep(500);
                }
            }

            CollectAllGarbage();
            
            Console.WriteLine();
            Console.WriteLine("End of demo 1. Hit <Enter> to continue.");
            Console.WriteLine();
            Console.WriteLine();
            Console.ReadLine();
        }

        private static void TestPerformanceOfByteArraysAllocationsStraightVersusPrePooled()
        {
            Console.WriteLine("Performance tests starting...");

            var arraysToPool = new List<byte[]>();

            for (int i = 0; i < NumberOfIterations; i++)
            {
                arraysToPool.Add(new byte[TestByteArraySize]);
            }

            var stopwatch = Stopwatch.StartNew();

            for (int i = 0; i < NumberOfIterations; i++)
            {
                _bytes = new byte[TestByteArraySize];
            }

            stopwatch.Stop();

            Console.WriteLine("Testing byte arrays...");

            Console.WriteLine("Pure allocations (ms): " + stopwatch.ElapsedMilliseconds);

            CollectAllGarbage();

            Warmup(arraysToPool);

            using (var byteArrayPool =
                Pools.CreateStaticPoolToleratingStarvation(
                    arraysToPool,
                    new ReusePreparationFunction<byte[]>(x=>x.Initialize(), ThreadingContract.Multithreaded),
                    new RepairStrategyNotSupportingRepairs<byte[]>(),
                    new ResourceCreationFunction<byte[]>(()=>
                                                             {
                                                                 return new byte[TestByteArraySize];
                                                             }, ThreadingContract.Multithreaded)))
            {
                stopwatch = Stopwatch.StartNew();

                GetAllBytesReturningAllBack(byteArrayPool);

                stopwatch.Stop();
            }

            Console.WriteLine("Getting instances and returning them back. Static pool (ms): " + stopwatch.ElapsedMilliseconds);
            Console.WriteLine("Hit <Enter> to continue.");
            Console.ReadLine();
            Console.WriteLine();
            Console.WriteLine();

            CollectAllGarbage();
        }

        /// <summary>
        /// JIT-compilation and stuff.
        /// </summary>
        /// <param name="arraysToPool"></param>
        private static void Warmup(List<byte[]> arraysToPool)
        {
            using (
                var byteArrayPool = Pools.CreateStaticPoolToleratingStarvation<byte[]>(arraysToPool,
                    new ReusePreparationFunction<byte[]>(x => x.Initialize(), ThreadingContract.Multithreaded),
                    new RepairFunction<byte[]>(x => { return x; }, ThreadingContract.Multithreaded),
                    new ResourceCreationFunction<byte[]>(()=>
                                                             {
                                                                 return new byte[TestByteArraySize];
                                                             }, ThreadingContract.Multithreaded)))
            {
                GetAllBytesReturningAllBack(byteArrayPool);
            }

            CollectAllGarbage();
        }

        private static void GetAllBytesReturningAllBack(Pool<byte[]> byteArrayPool)
        {
            var arrays = new List<byte[]>(NumberOfIterations);

            for (int i = 0; i < NumberOfIterations; i++)
            {
                // Note: if you were not planning on returning the resources, you should've used pool.GiveUp() instead of pool.Lend();
                arrays.Add(byteArrayPool.Lend());
            }

            for (int i = 0; i < arrays.Count; i++)
            {
                // Note: if you were not planning on returning the resources, you should've used pool.GiveUp() instead of pool.Lend();
                byteArrayPool.AcceptBack(arrays[i]);
            }
        }

        private static void CollectAllGarbage()
        {
            GC.Collect();
            GC.WaitForPendingFinalizers();
            GC.Collect();
        }

        private static void TestPerformanceOfArraysOfReferenceAllocationsStraightVersusPrePooled()
        {
            Console.WriteLine("Testing reference types...");

            var referenceArraysToPool = new List<MyReferenceType[]>();

            for (int i = 0; i < NumberOfIterations; i++)
            {
                referenceArraysToPool.Add(new MyReferenceType[ReferenceTypesArrayLength]);
            }

            var stopwatch = Stopwatch.StartNew();

            for (int i = 0; i < NumberOfIterations; i++)
            {
                var bytes = new MyReferenceType[ReferenceTypesArrayLength];

                bytes[0] = new MyReferenceType("", "", "", 12);
            }

            stopwatch.Stop();

            Console.WriteLine("Pure allocations (ms): " + stopwatch.ElapsedMilliseconds);

            CollectAllGarbage();

            using (var referenceTypeArrayPool = 
                Pools.CreateStaticPoolToleratingStarvation(
                    referenceArraysToPool,
                    new ReusePreparationFunction<MyReferenceType[]>(x => x.Initialize(), ThreadingContract.Multithreaded),
                    new RepairStrategyNotSupportingRepairs<MyReferenceType[]>(),
                    new ResourceCreationFunction<MyReferenceType[]>(() => { return new MyReferenceType[TestByteArraySize]; }, ThreadingContract.Multithreaded)))
            {
                stopwatch = Stopwatch.StartNew();

                GetAllReturningAllBack(referenceTypeArrayPool);

                stopwatch.Stop();
            }

            Console.WriteLine("Getting instances and returning them back. Static pool (ms): " + stopwatch.ElapsedMilliseconds);
        }

        private static void GetAllReturningAllBack(Pool<MyReferenceType[]> referenceTypeArrayPool)
        {
            for (int i = 0; i < NumberOfIterations; i++)
            {
                MyReferenceType[] array;

                array = referenceTypeArrayPool.Lend();

                array[0] = new MyReferenceType("", "", "", 12);

                referenceTypeArrayPool.AcceptBack(array);
            }
        }
    }
}